package reports;

import static org.junit.Assert.*;

import java.text.ParseException;
import java.util.Date;

import model.entity.Product;

import org.junit.Test;

import reports.visitor.IVisitor;

import common.Size;
import common.util.DateUtils;

/**
 * Tests the Product Statistics Report based on the report period.
 * 
 * @author Ryan Cheatham
 */
public class ProductStatisticsReportPeriodTest extends ProductStatisticsReportTestSuperclass
{
  @Test
  public void testReportPeriodInDomain()
  {
    try
    {
      ProductStatisticsReport report;

      final Date today = DateUtils.parseDate("01/01/2013");
      DateUtils.setCurrentDate(today);

      setItemManagerVisitorNode(new MockNode()
      {
        @Override
        public void preOrderAccept(IVisitor visitor)
        {
          try
          {
            // generate 1 item one day after end of report period
            Date entryDate = DateUtils.parseDate("01/02/2013");
            generateItem(entryDate).preOrderAccept(visitor);

            // generate 2 items on end day of report period
            for (int i = 0; i < 2; i++)
              generateItem(today).preOrderAccept(visitor);

            // generate 3 items one day before end of report period
            entryDate = DateUtils.parseDate("12/31/2012");
            for (int i = 0; i < 3; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // one month ago

            // generate 4 items one month before end of report period, less one day
            entryDate = DateUtils.parseDate("12/02/2012");
            for (int i = 0; i < 4; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // generate 5 items one month before end of report period
            entryDate = DateUtils.parseDate("12/01/2012");
            for (int i = 0; i < 5; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // generate 6 items one month and one day before end of report period
            entryDate = DateUtils.parseDate("11/30/2012");
            for (int i = 0; i < 6; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // 12 months ago

            // generate 7 items twelve months before end of report period, less one day
            entryDate = DateUtils.parseDate("01/02/2012");
            for (int i = 0; i < 7; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // generate 8 items twelve months before end of report period
            entryDate = DateUtils.parseDate("01/01/2012");
            for (int i = 0; i < 8; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // generate 9 items twelve months and one day before end of report period
            entryDate = DateUtils.parseDate("12/31/2011");
            for (int i = 0; i < 9; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // 100 months ago

            // generate 10 items one hundred months before end of report period, less one day
            entryDate = DateUtils.parseDate("09/02/2004");
            for (int i = 0; i < 10; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // generate 11 items one hundred months before end of report period
            entryDate = DateUtils.parseDate("09/01/2004");
            for (int i = 0; i < 11; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // generate 12 items one hundred months and one day before end of report period
            entryDate = DateUtils.parseDate("08/31/2004");
            for (int i = 0; i < 12; i++)
              generateItem(entryDate).preOrderAccept(visitor);
          }
          catch (ParseException e)
          {
            fail(e.getMessage());
          }
        }
      });

      setProductManagerVisitorNode(new MockNode()
      {
        @Override
        public void preOrderAccept(IVisitor visitor)
        {
          getDefaultProduct().preOrderAccept(visitor);
        }
      });

      Product product = getDefaultProduct();

      // //////////////////////////////////////////////////////////////////////////////////////////
      // a. Input month is a little in the upper domain
      // //////////////////////////////////////////////////////////////////////////////////////////

      report = generateReport(100);
      int total = 77;
      int added = 65;
      int addedOnFirstDay = 11;
      int minSupply = total - added + addedOnFirstDay;
      int maxAge = 3045;

      assertEquals(total, report.getCurrentSupply(product));
      assertEquals(36.0509f, report.getAverageSupply(product), 0.1f);

      assertEquals(minSupply, report.getMinSupply(product));
      assertEquals(total, report.getMaxSupply(product));

      assertEquals(0, report.getQuantityUsed(product));
      assertEquals(added, report.getQuantityAdded(product));

      assertTrue(Float.isNaN(report.getAverageUsedAge(product)));
      assertEquals(0, report.getMaxUsedAge(product));

      assertEquals(1424.8052f, report.getAverageCurrentAge(product), 0.1f);
      assertEquals(maxAge, report.getMaxCurrentAge(product));

      // //////////////////////////////////////////////////////////////////////////////////////////
      // b. Input month is a little in the lower domain
      // //////////////////////////////////////////////////////////////////////////////////////////

      report = generateReport(1);
      added = 14;
      addedOnFirstDay = 5;
      minSupply = total - added + addedOnFirstDay;

      assertEquals(total, report.getCurrentSupply(product));
      assertEquals(72.125f, report.getAverageSupply(product), 0.1f);

      assertEquals(minSupply, report.getMinSupply(product));
      assertEquals(total, report.getMaxSupply(product));

      assertEquals(0, report.getQuantityUsed(product));
      assertEquals(added, report.getQuantityAdded(product));

      assertTrue(Float.isNaN(report.getAverageUsedAge(product)));
      assertEquals(0, report.getMaxUsedAge(product));

      assertEquals(1424.8052f, report.getAverageCurrentAge(product), 0.1f);
      assertEquals(maxAge, report.getMaxCurrentAge(product));

      // //////////////////////////////////////////////////////////////////////////////////////////
      // c. Input month is in the middle of the domain.
      // //////////////////////////////////////////////////////////////////////////////////////////

      report = generateReport(12);
      added = 35;
      addedOnFirstDay = 8;
      minSupply = total - added + addedOnFirstDay;

      assertEquals(total, report.getCurrentSupply(product));
      assertEquals(58.3161f, report.getAverageSupply(product), 0.1f);

      assertEquals(minSupply, report.getMinSupply(product));
      assertEquals(total, report.getMaxSupply(product));

      assertEquals(0, report.getQuantityUsed(product));
      assertEquals(added, report.getQuantityAdded(product));

      assertTrue(Float.isNaN(report.getAverageUsedAge(product)));
      assertEquals(0, report.getMaxUsedAge(product));

      assertEquals(1424.8052f, report.getAverageCurrentAge(product), 0.1f);
      assertEquals(maxAge, report.getMaxCurrentAge(product));
    }
    catch (ParseException e)
    {
      fail(e.getMessage());
    }
  }

  @Test
  public void transitionIntoDSTTest()
  {
    Product product = generateProduct();
    product.setShelfLife(1);
    product.setSize(new Size());
    product.setThreeMonthSupply(1);
    setDefaultProduct(product);

    try
    {
      final Date monthBefore = DateUtils.parseDate("02/10/2013");
      // this is the day Daylight Savings Time started in 2013
      final Date DSTStartDate = DateUtils.parseDate("03/10/2013");
      final Date monthAfter = DateUtils.parseDate("04/10/2013");

      setItemManagerVisitorNode(new MockNode()
      {
        @Override
        public void preOrderAccept(IVisitor visitor)
        {
          try
          {
            // add 1 item the day before the month before DST start 2013
            Date entryDate = DateUtils.parseDate("02/09/2013");
            generateItem(entryDate).preOrderAccept(visitor);

            // add 2 items the month before DST start 2013
            for (int i = 0; i < 2; i++)
              generateItem(monthBefore).preOrderAccept(visitor);

            // add 3 items the day after the month before DST start 2013
            entryDate = DateUtils.parseDate("02/11/2013");
            for (int i = 0; i < 3; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // add 4 items the day before DST start 2013
            entryDate = DateUtils.parseDate("03/09/2013");
            for (int i = 0; i < 4; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // add 5 items on DST start 2013
            for (int i = 0; i < 5; i++)
              generateItem(DSTStartDate).preOrderAccept(visitor);

            // add 6 items the day after DST start 2013
            entryDate = DateUtils.parseDate("03/11/2013");
            for (int i = 0; i < 6; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // add 7 items the day before the month after DST start 2013
            entryDate = DateUtils.parseDate("04/09/2013");
            for (int i = 0; i < 7; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // add 8 items the month after DST start 2013
            for (int i = 0; i < 8; i++)
              generateItem(monthAfter).preOrderAccept(visitor);

            // add 9 items the day after the month after DST start 2013
            entryDate = DateUtils.parseDate("04/11/2013");
            for (int i = 0; i < 9; i++)
              generateItem(entryDate).preOrderAccept(visitor);
          }
          catch (ParseException e)
          {
            fail(e.getMessage());
          }
        }
      });

      setProductManagerVisitorNode(new MockNode()
      {
        @Override
        public void preOrderAccept(IVisitor visitor)
        {
          getDefaultProduct().preOrderAccept(visitor);
        }
      });

      ProductStatisticsReport report;

      // //////////////////////////////////////////////////////////////////////////////////////////
      // a. The transition occurs in the middle of the report period
      // //////////////////////////////////////////////////////////////////////////////////////////

      DateUtils.setCurrentDate(monthAfter);
      report = generateReport(2);
      int total = 36;
      int added = 35;
      int addedOnFirstDay = 2;
      int minSupply = total - added + addedOnFirstDay;
      int maxAge = 60;

      assertEquals(total, report.getCurrentSupply(product));
      assertEquals(14.2833f, report.getAverageSupply(product), 0.1f);

      assertEquals(minSupply, report.getMinSupply(product));
      assertEquals(total, report.getMaxSupply(product));

      assertEquals(0, report.getQuantityUsed(product));
      assertEquals(added, report.getQuantityAdded(product));

      assertTrue(Float.isNaN(report.getAverageUsedAge(product)));
      assertEquals(0, report.getMaxUsedAge(product));

      assertEquals(22.8333f, report.getAverageCurrentAge(product), 0.1f);
      assertEquals(maxAge, report.getMaxCurrentAge(product));

      // //////////////////////////////////////////////////////////////////////////////////////////
      // b. The transition occurs on the first day of the report period
      // //////////////////////////////////////////////////////////////////////////////////////////

      report = generateReport(1);
      added = 26;
      addedOnFirstDay = 5;
      minSupply = total - added + addedOnFirstDay;

      assertEquals(total, report.getCurrentSupply(product));
      assertEquals(21.5f, report.getAverageSupply(product), 0.1f);

      assertEquals(minSupply, report.getMinSupply(product));
      assertEquals(total, report.getMaxSupply(product));

      assertEquals(0, report.getQuantityUsed(product));
      assertEquals(added, report.getQuantityAdded(product));

      assertTrue(Float.isNaN(report.getAverageUsedAge(product)));
      assertEquals(0, report.getMaxUsedAge(product));

      assertEquals(22.8333f, report.getAverageCurrentAge(product), 0.1f);
      assertEquals(maxAge, report.getMaxCurrentAge(product));

      // //////////////////////////////////////////////////////////////////////////////////////////
      // c. The transition occurs on the last day of the report period
      // //////////////////////////////////////////////////////////////////////////////////////////

      DateUtils.setCurrentDate(DSTStartDate);
      report = generateReport(2);
      total = 15;
      added = 15;
      addedOnFirstDay = 0;
      minSupply = total - added + addedOnFirstDay;
      maxAge = 29;

      assertEquals(total, report.getCurrentSupply(product));
      assertEquals(3.0833f, report.getAverageSupply(product), 0.1f);

      assertEquals(minSupply, report.getMinSupply(product));
      assertEquals(total, report.getMaxSupply(product));

      assertEquals(0, report.getQuantityUsed(product));
      assertEquals(added, report.getQuantityAdded(product));

      assertTrue(Float.isNaN(report.getAverageUsedAge(product)));
      assertEquals(0, report.getMaxUsedAge(product));

      assertEquals(11.3333f, report.getAverageCurrentAge(product), 0.1f);
      assertEquals(maxAge, report.getMaxCurrentAge(product));
    }
    catch (ParseException e)
    {
      fail(e.getMessage());
    }
  }

  @Test
  public void transitionOutOfDSTTest()
  {
    try
    {
      final Date monthBefore = DateUtils.parseDate("10/03/2013");
      final Date DSTEndDate = DateUtils.parseDate("11/03/2013");
      final Date monthAfter = DateUtils.parseDate("12/03/2013");

      setItemManagerVisitorNode(new MockNode()
      {
        @Override
        public void preOrderAccept(IVisitor visitor)
        {
          try
          {
            // add 1 item the day before the month before DST end 2013
            Date entryDate = DateUtils.parseDate("10/02/2013");
            generateItem(entryDate).preOrderAccept(visitor);

            // add 2 items the month before DST end 2013
            for (int i = 0; i < 2; i++)
              generateItem(monthBefore).preOrderAccept(visitor);

            // add 3 items the day after the month before DST end 2013
            entryDate = DateUtils.parseDate("10/04/2013");
            for (int i = 0; i < 3; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // add 4 items the day before DST end 2013
            entryDate = DateUtils.parseDate("11/02/2013");
            for (int i = 0; i < 4; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // add 5 items on DST end 2013
            for (int i = 0; i < 5; i++)
              generateItem(DSTEndDate).preOrderAccept(visitor);

            // add 6 items the day after DST end 2013
            entryDate = DateUtils.parseDate("11/04/2013");
            for (int i = 0; i < 6; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // add 7 items the day before the month after DST end 2013
            entryDate = DateUtils.parseDate("12/02/2013");
            for (int i = 0; i < 7; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // add 8 items the month after DST end 2013
            for (int i = 0; i < 8; i++)
              generateItem(monthAfter).preOrderAccept(visitor);

            // add 9 items the day after the month after DST end 2013
            entryDate = DateUtils.parseDate("12/04/2013");
            for (int i = 0; i < 9; i++)
              generateItem(entryDate).preOrderAccept(visitor);
          }
          catch (ParseException e)
          {
            fail(e.getMessage());
          }
        }
      });

      setProductManagerVisitorNode(new MockNode()
      {
        @Override
        public void preOrderAccept(IVisitor visitor)
        {
          getDefaultProduct().preOrderAccept(visitor);
        }
      });

      ProductStatisticsReport report;
      Product product = getDefaultProduct();

      // //////////////////////////////////////////////////////////////////////////////////////////
      // a. The transition occurs in the middle of the report period
      // //////////////////////////////////////////////////////////////////////////////////////////

      DateUtils.setCurrentDate(monthAfter);
      report = generateReport(2);
      int total = 36;
      int added = 35;
      int addedOnFirstDay = 2;
      int minSupply = total - added + addedOnFirstDay;
      int maxAge = 62;

      assertEquals(total, report.getCurrentSupply(product));
      assertEquals(13.7742f, report.getAverageSupply(product), 0.1f);

      assertEquals(minSupply, report.getMinSupply(product));
      assertEquals(total, report.getMaxSupply(product));

      assertEquals(0, report.getQuantityUsed(product));
      assertEquals(added, report.getQuantityAdded(product));

      assertTrue(Float.isNaN(report.getAverageUsedAge(product)));
      assertEquals(0, report.getMaxUsedAge(product));

      assertEquals(22.75f, report.getAverageCurrentAge(product), 0.1f);
      assertEquals(maxAge, report.getMaxCurrentAge(product));

      // //////////////////////////////////////////////////////////////////////////////////////////
      // b. The transition occurs on the first day of the report period
      // //////////////////////////////////////////////////////////////////////////////////////////

      report = generateReport(1);
      added = 26;
      addedOnFirstDay = 5;
      minSupply = total - added + addedOnFirstDay;

      assertEquals(total, report.getCurrentSupply(product));
      assertEquals(21.5161f, report.getAverageSupply(product), 0.1f);

      assertEquals(minSupply, report.getMinSupply(product));
      assertEquals(total, report.getMaxSupply(product));

      assertEquals(0, report.getQuantityUsed(product));
      assertEquals(added, report.getQuantityAdded(product));

      assertTrue(Float.isNaN(report.getAverageUsedAge(product)));
      assertEquals(0, report.getMaxUsedAge(product));

      assertEquals(22.75f, report.getAverageCurrentAge(product), 0.1f);
      assertEquals(maxAge, report.getMaxCurrentAge(product));

      // //////////////////////////////////////////////////////////////////////////////////////////
      // c. The transition occurs on the last day of the report period
      // //////////////////////////////////////////////////////////////////////////////////////////

      DateUtils.setCurrentDate(DSTEndDate);
      report = generateReport(2);
      total = 15;
      added = 15;
      addedOnFirstDay = 0;
      minSupply = total - added + addedOnFirstDay;
      maxAge = 32;

      assertEquals(total, report.getCurrentSupply(product));
      assertEquals(3.2581f, report.getAverageSupply(product), 0.1f);

      assertEquals(minSupply, report.getMinSupply(product));
      assertEquals(total, report.getMaxSupply(product));

      assertEquals(0, report.getQuantityUsed(product));
      assertEquals(added, report.getQuantityAdded(product));

      assertTrue(Float.isNaN(report.getAverageUsedAge(product)));
      assertEquals(0, report.getMaxUsedAge(product));

      assertEquals(12.5333f, report.getAverageCurrentAge(product), 0.1f);
      assertEquals(maxAge, report.getMaxCurrentAge(product));
    }
    catch (ParseException e)
    {
      fail(e.getMessage());
    }
  }

  @Test
  public void yearTransitionTest()
  {
    try
    {
      final Date newYear = DateUtils.parseDate("01/01/2013");
      final Date monthAfter = DateUtils.parseDate("02/01/2013");
      final Date monthBefore = DateUtils.parseDate("12/01/2012");

      setItemManagerVisitorNode(new MockNode()
      {
        @Override
        public void preOrderAccept(IVisitor visitor)
        {
          try
          {
            // add 1 item the day before the month before New Years 2013
            Date entryDate = DateUtils.parseDate("11/30/2012");
            generateItem(entryDate).preOrderAccept(visitor);

            // add 2 items the month before New Years 2013
            for (int i = 0; i < 2; i++)
              generateItem(monthBefore).preOrderAccept(visitor);

            // add 3 items the day after the month before New Years 2013
            entryDate = DateUtils.parseDate("12/02/2012");
            for (int i = 0; i < 3; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // add 4 items the day before New Years 2013
            entryDate = DateUtils.parseDate("12/31/2012");
            for (int i = 0; i < 4; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // add 5 items on New Years 2013
            for (int i = 0; i < 5; i++)
              generateItem(newYear).preOrderAccept(visitor);

            // add 6 items the day after New Years 2013
            entryDate = DateUtils.parseDate("01/02/2013");
            for (int i = 0; i < 6; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // add 7 items the day before the month after New Years 2013
            entryDate = DateUtils.parseDate("01/31/2013");
            for (int i = 0; i < 7; i++)
              generateItem(entryDate).preOrderAccept(visitor);

            // add 8 items the month after New Years 2013
            for (int i = 0; i < 8; i++)
              generateItem(monthAfter).preOrderAccept(visitor);

            // add 9 items the day after the month after New Years 2013
            entryDate = DateUtils.parseDate("02/02/2013");
            for (int i = 0; i < 9; i++)
              generateItem(entryDate).preOrderAccept(visitor);
          }
          catch (ParseException e)
          {
            fail(e.getMessage());
          }
        }
      });

      setProductManagerVisitorNode(new MockNode()
      {
        @Override
        public void preOrderAccept(IVisitor visitor)
        {
          getDefaultProduct().preOrderAccept(visitor);
        }
      });

      ProductStatisticsReport report;
      Product product = getDefaultProduct();

      // //////////////////////////////////////////////////////////////////////////////////////////
      // a. The transition occurs in the middle of the report period
      // //////////////////////////////////////////////////////////////////////////////////////////

      DateUtils.setCurrentDate(monthAfter);
      report = generateReport(2);
      int total = 36;
      int added = 35;
      int addedOnFirstDay = 2;
      int minSupply = total - added + addedOnFirstDay;
      int maxAge = 63;

      assertEquals(total, report.getCurrentSupply(product));
      assertEquals(13.8889f, report.getAverageSupply(product), 0.1f);

      assertEquals(minSupply, report.getMinSupply(product));
      assertEquals(total, report.getMaxSupply(product));

      assertEquals(0, report.getQuantityUsed(product));
      assertEquals(added, report.getQuantityAdded(product));

      assertTrue(Float.isNaN(report.getAverageUsedAge(product)));
      assertEquals(0, report.getMaxUsedAge(product));

      assertEquals(23.333333f, report.getAverageCurrentAge(product), 0.1f);
      assertEquals(maxAge, report.getMaxCurrentAge(product));

      // //////////////////////////////////////////////////////////////////////////////////////////
      // b. The transition occurs on the first day of the report period
      // //////////////////////////////////////////////////////////////////////////////////////////

      report = generateReport(1);
      added = 26;
      addedOnFirstDay = 5;
      minSupply = total - added + addedOnFirstDay;

      assertEquals(total, report.getCurrentSupply(product));
      assertEquals(21.5f, report.getAverageSupply(product), 0.1f);

      assertEquals(minSupply, report.getMinSupply(product));
      assertEquals(total, report.getMaxSupply(product));

      assertEquals(0, report.getQuantityUsed(product));
      assertEquals(added, report.getQuantityAdded(product));

      assertTrue(Float.isNaN(report.getAverageUsedAge(product)));
      assertEquals(0, report.getMaxUsedAge(product));

      assertEquals(23.333333f, report.getAverageCurrentAge(product), 0.1f);
      assertEquals(maxAge, report.getMaxCurrentAge(product));

      // //////////////////////////////////////////////////////////////////////////////////////////
      // c. The transition occurs on the last day of the report period
      // //////////////////////////////////////////////////////////////////////////////////////////

      DateUtils.setCurrentDate(newYear);
      report = generateReport(2);
      total = 15;
      added = 15;
      addedOnFirstDay = 0;
      minSupply = total - added + addedOnFirstDay;
      maxAge = 32;

      assertEquals(total, report.getCurrentSupply(product));
      assertEquals(3.2581f, report.getAverageSupply(product), 0.1f);

      assertEquals(minSupply, report.getMinSupply(product));
      assertEquals(total, report.getMaxSupply(product));

      assertEquals(0, report.getQuantityUsed(product));
      assertEquals(added, report.getQuantityAdded(product));

      assertTrue(Float.isNaN(report.getAverageUsedAge(product)));
      assertEquals(0, report.getMaxUsedAge(product));

      assertEquals(12.5333333f, report.getAverageCurrentAge(product), 0.1f);
      assertEquals(maxAge, report.getMaxCurrentAge(product));
    }
    catch (ParseException e)
    {
      fail(e.getMessage());
    }
  }
}
